package com.fengwk.support.code;

import java.io.ByteArrayOutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import com.fengwk.support.constant.SupportConstant;
import com.fengwk.support.exception.BaseException;
import com.fengwk.support.util.CodeUtils;
import com.fengwk.support.util.IoUtils;

/**
 * RSA秘钥
 * 
 * @author fengwk
 *
 */
public class RSAKey implements Serializable {
	
	private static final long serialVersionUID = -177380205545651409L;
	
	protected Key key;
	
	protected RSAKey(Key key) {
		this.key = key;
	}
	
	/**
	 * 返回编码格式的秘钥
	 * 
	 * @return
	 */
	public byte[] encoded() {
		return key.getEncoded();
	}
	
	/**
	 * 返回16进制编码格式的秘钥
	 * 
	 * @return
	 */
	public String encodedHex() {
		return CodeUtils.binary2hex(key.getEncoded());
	}
	
	/**
	 * 加密
	 * 
	 * @param data
	 * @return
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 */
	public byte[] encrypt(byte[] data) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
		Cipher cipher = Cipher.getInstance(RSA.ALGORITHM_RSA);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return rsaSplitCodec(
       		 cipher, 
       		 Cipher.ENCRYPT_MODE, 
       		 data, 
       		 ((java.security.interfaces.RSAKey) key).getModulus().bitLength());
	}
	
	/**
	 * 加密
	 * 
	 * @param data
	 * @return
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 * @throws UnsupportedEncodingException 
	 */
	public byte[] encrypt(String data) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException {
		return encrypt(data.getBytes(SupportConstant.CHARSET_UTF8));
	}
	
	/**
	 * 加密为16进制编码
	 * 
	 * @param data
	 * @return
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 */
	public String encrypt2hex(byte[] data) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
		return CodeUtils.binary2hex(encrypt(data));
	}
	
	/**
	 * 加密为16进制编码
	 * 
	 * @param data
	 * @return
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 */
	public String encrypt2hex(String data) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
		return encrypt2hex(data.getBytes());
	}
   
   /**
    * 解密
    * 
    * @param data
    * @return
    * @throws InvalidKeyException
    * @throws NoSuchAlgorithmException
    * @throws NoSuchPaddingException
    */
   public byte[] decrypt(byte[] data) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
	   Cipher cipher = Cipher.getInstance(RSA.ALGORITHM_RSA);
       cipher.init(Cipher.DECRYPT_MODE, key);
       return rsaSplitCodec(
       		cipher, 
       		Cipher.DECRYPT_MODE, 
       		data, 
       		((java.security.interfaces.RSAKey) key).getModulus().bitLength());
   }
   
   /**
    * 解密
    * 
    * @param data
    * @return
    * @throws InvalidKeyException
    * @throws NoSuchAlgorithmException
    * @throws NoSuchPaddingException
    */
   public byte[] decrypt(String data) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
	   return decrypt(CodeUtils.hex2binary(data));
   }
   
   /**
    * 解密为字符串形式
    * 
    * @param data
    * @return
    * @throws InvalidKeyException
    * @throws UnsupportedEncodingException
    * @throws NoSuchAlgorithmException
    * @throws NoSuchPaddingException
    */
   public String decrypt2str(byte[] data) throws InvalidKeyException, UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException {
	   return new String(decrypt(data), SupportConstant.CHARSET_UTF8);
   }
   
   /**
    * 解密为字符串形式
    * 
    * @param data
    * @return
    * @throws InvalidKeyException
    * @throws UnsupportedEncodingException
    * @throws NoSuchAlgorithmException
    * @throws NoSuchPaddingException
    */
   public String decrypt2str(String data) throws InvalidKeyException, UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException {
	   return decrypt2str(CodeUtils.hex2binary(data));
   }
   
   /**
    * 分割运算
    * SA加密算法对于加密数据的长度是有要求的,一般来说,明文长度小于等于密钥长度(byte)-11
    * 将数据分割为合适长度再进行加密
    * 
    * @param cipher
    * @param opmode
    * @param datas
    * @param keySize
    * @return
    */
   private byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {
	   // 计算单片长度
       int maxBlock = 0;
       if(opmode == Cipher.DECRYPT_MODE)
           maxBlock = keySize / 8;
       else
           maxBlock = keySize / 8 - 11;
       // 构建输出字节流
       ByteArrayOutputStream out = new ByteArrayOutputStream();
       int offSet = 0;
       byte[] buff;
       int i = 0;
       while(datas.length > offSet){
           if(datas.length-offSet > maxBlock){
               try {
					buff = cipher.doFinal(datas, offSet, maxBlock);
				} catch (IllegalBlockSizeException | BadPaddingException e) {
					throw new BaseException(e.getMessage());
				} finally {
					IoUtils.close(out);
				}
           }else{
               try {
					buff = cipher.doFinal(datas, offSet, datas.length-offSet);
				} catch (IllegalBlockSizeException | BadPaddingException e) {
					throw new BaseException(e.getMessage());
				} finally {
					IoUtils.close(out);
				}
           }
           out.write(buff, 0, buff.length);
           i++;
           offSet = i * maxBlock;
       }
       byte[] res = out.toByteArray();
       IoUtils.close(out);
       return res;
   }
   
}
